<?php
/**
 * Classe Model
 *
 * TODO: Primary Keys = date?
 */
abstract class SimpleModel extends Model {
	
	/**
	 * Renvoie le nom de la table SQL
	 */
	
	public abstract function sqltable();
	

	
	public static function getOption($options, $name, $default = null) {
		$v = $default;
		if (isset($options[$name])) {
			$v = $options[$name];
		}
		return $v;
	}
	
	/**
	 * Récupère la valeur SQL d'une valeur d'un type précisé
	 */
	public static function getValueSQL($type, $value, $options = array()) {
		$value = self::getValueFromType($type, $value, $options);
		if ($type == "bool" || $type == "boolean") {
			if ($value) {
				$value = 1;
			}
			else {
				$value = 0;
			}
		}
		return $value;
	}

	
	/**
	 * Renvoie l'ensemble des primary keys
	 */
	public function getPrimaryKeys() {
	
		$schema = $this->getSchema();
		$keys = array();

		foreach ($schema as $name => $value) {
			if (!empty($value['primary'])) {
				$keys[] = $name;
			}
		}
		return $keys;
	}
	
	/**
	 * Indique si les clés primaires sont vides (et donc insertion)
	 */
	public function isPrimaryKeysEmpties() {
		$primary = $this->getPrimaryKeys();
		$empty = true;
		foreach ($primary as $p) {
			if (!empty($this->$p)) {
				$empty = false;
				break;
			}
		}
		return $empty;
	}
	
	
	/**
	 * Charge un objet précis
	 * Peut être override avec une requête
	 */
	public function load($id, $options = array()) {
		$primarykeys = $this->getPrimaryKeys();

		$schema = $this->getSchema();
		
		if (!is_array($id)) {
			$id = array($id);
		}
		
		// Pour les jointures
		if (!empty($options['query'])) {
			$sql = $options['query'];
		}
		
		if (empty($sql)) {
			$sql = "SELECT * FROM ".$this->sqltable();
		}
		$where = array();
		foreach ($primarykeys as $i => $pk) {
			$primarykey = $schema[$pk];
			$sqlcolumn = $primarykey['sqlcolumn_name'];
			$where[] = array('AND', "$sqlcolumn = ?", $id[$i], $primarykey['type']);
		}

		$st = self::buildQuery(array(
			'query' => $sql,
			'where' => $where
		));
		$st->fetch();
		$this->init($st);
	}
	
	/**
	 * Supprime l'objet courant
	 */
	public function delete() {
		$primarykeys = $this->getPrimaryKeys();
		$isMultiplePrimary = (count($primarykeys) > 1);
		$schema = $this->getSchema();
		$sql = "DELETE FROM ".$this->sqltable();
		foreach ($primarykeys as $i => $pkname) {
			$primarykey = $schema[$pkname];
			$sqlcolumn = $primarykey['sqlcolumn_name'];
			
			$where[] = array('AND', "$sqlcolumn = ?", $this->$pkname, $primarykey['type']);
		}
		$st = self::buildQuery(array(
			'query' => $sql,
			'where' => $where
		));
		return $st->isSuccess();
	}
	
	
	/**
	 * Sauvegarde l'objet courant
	 */
	public function save($options = array()) {

		$schema = $this->getSchema();
		
		$insert = self::getOption($options, "insert", null);
		$attributes = self::getOption($options, "attributes", null);
		$tablename = self::getOption($options, "table", $this->sqltable());
		
		if (is_null($insert)) {
			$insert = $this->isPrimaryKeysEmpties();
		}
		
		if (is_null($attributes)) {
			foreach ($schema as $attrname => $data) {
				if (empty($data['primary'])) {
					$attr = $attrname;
					$attributes[] = $attr;
				}
			}
		}
		
		$where = array();
		$values = array();
		$primarykeys = $this->getPrimaryKeys();
		
		if (!$insert) {
			foreach ($primarykeys as $pk) {
				$sqlcolumn = $schema[$pk]['sqlcolumn_name'];
				$type = $schema[$pk]['type'];
				$where[] = array('AND', "$sqlcolumn = ?", $this->$pk, $type);
			}
		}
		
		$query = "UPDATE $tablename";
		$columns = array();
		$values = array();
		
		if ($insert) {
			$query = "INSERT INTO $tablename";
		}
		
		foreach ($attributes as $attrname) {
			$type = $schema[$attrname]['type'];
			$columnName = $schema[$attrname]['sqlcolumn_name'];

			$values[] = self::getValueSQL($type, $this->$attrname, $this->getOptions());
			
			if ($insert) {
				$columns[] = $columnName;
			} else {
				$columns[] = $columnName." = ?";
			}
		}

		if ($insert) {
			$query .= ' ('.implode(',',$columns).') VALUES ';
			$vars = self::array_str_repeat("?", count($columns));
			$query .= '('.implode(',',$vars).')';
		}
		else {
			$query .= " SET ".implode(",",$columns);
		}
		
		$st = self::buildQuery(array(
			'query' => $query,
			'where' => $where,
			'values' => $values
		));
		// Auto increment
		if ($insert) {
			if (count($primarykeys) == 1) {
				$pk = $primarykeys[0];
				$lastid = $st->lastInsertId();
				if (!empty($lastid)) {
					$this->$pk = $lastid;
				}
			}
		}
		if (!$st->isSuccess()) {
			$error_info = $st->errorInfo();
			error_log('Error "'.$error_info[2].'" for query '.$query);
		}

		return $st->isSuccess();
	}

	
	
	/**
	 * Permet d'exécuter une requête avec les options suivantes
	 * query: la base de la recherche: peut être un DELETE FROM table ou un SELECT * FROM table. Contient les jointures manuelles
	 * where: les conditions where, dans un tableau. Chaque tableau contient 2 éléments, la jointure (AND ou OR) et en deuxième la condition. La condition peut être aussi un ensemble de conditions (récursif)
	 * groupby: la clause groupby
	 * having: la clause having, identique à where
	 * limit: nombre d'enregistrements (compatible uniquement MySQL et PostgreSQL)
	 * offset: index à partir lequel commencer (compatible uniquement MySQL et PostgreSQL)
	 * page: page à partir de laquelle commencer (de 1 à X, compatible uniquement MySQL et PostgreSQL)
	 *
	 * @return PDOStatement le statement correspondant à la requête executée
	 */
	public static function buildQuery($options = array()) {
		$query = self::getOption($options, "query", "");
		$where = self::getOption($options, "where", array());
		$groupby = self::getOption($options, "groupby", "");
		$orderby = self::getOption($options, "orderby", "");
		$having = self::getOption($options, "having", array());
		$limit = self::getOption($options, "limit", 0);
		$offset = self::getOption($options, "offset", 0);
		$page = self::getOption($options, "page", 0);
		$values = self::getOption($options, "values", array());
		
		if (!empty($where) && is_array($where)) {
			$query .= " WHERE ".self::createWhereConditions($where, $values);
		}
		
		if (!empty($groupby)) {
			$query .= " GROUP BY $groupby";
		}

		if (!empty($having) && is_array($having)) {
			$query .= " HAVING ".self::createWhereConditions($having, $values);
		}
		
		if (!empty($orderby)) {
			$query .= " ORDER BY $orderby";
		}
		
		// Page: spécial aide aux dev pour la pagination, au lieu de renseigner le offset, uniquement le numéro de page 1 à X et le nombre de résultats (limit)
		if (empty($offset) && !empty($page) && !empty($limit)) {
			$offset = $limit*($page-1);
		}
		
		// Limit et offset: déclaration dépendante de chaque serveur (mysql LIMIT LIMIT,OFFSET, postgresql LIMIT LIMIT OFFSET OFFSET)
		$driver_name = self::getOption($options,"driver","");
		if (empty($driver_name)) {
			$db = self::getDB();
			$driver_name = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
		}

		switch($driver_name) {
			case 'mysql':
			case 'pgsql':
				if (!empty($limit)) {
					$query .= " LIMIT $limit";
				}
				if (!empty($offset)) {
					$query .= " OFFSET $offset";
				}
				break;

			default:
				break;
		}
		
		$db = self::getDB();
		$st = $db->prepareAndExecute($query, $values);
		error_log($query);
		return $st;
	}
	
	/**
	 * Crée la condition where selon le tableau 'conditions'
	 */
	public static function createWhereConditions($conditions, &$values = array()) {
		
		$where = "";
		$options = self::getStaticOptions();
		foreach ($conditions as $c) {
			
			$type = " AND ";
			$condition = $c;
			
			if (is_array($c) && count($c) >= 2) {
				$type = " ".$c[0]." ";
				$condition = $c[1];
				
				if (isset($c[2])) {
					$val = $c[2];
					if (!empty($c[3])) {
						$type = $c[3];
						if (!is_array($val)) {
							$val = self::getValueSQL($type, $val, $options);
						} else {
							foreach ($val as &$v) {
								$v = self::getValueSQL($type, $v, $options);
							}
						}
					}
					if (is_array($val)) { // Cas d'un IN ?
						$nb_in = array();
						foreach ($val as $v) {
							$nb_in[] = "?";
							$values[] = $v;
						}
						$condition = str_replace("IN ?", "IN (".implode(',',$nb_in).")", $condition);
					
					} else {
						$values[] = $val;
					}
				}
			}
			
			if (!empty($where)) {
				$where .= $type;
			}
			
			if (is_array($condition)) {
				$condition = "(".self::createWhereConditions($condition, $values, $options).")";
			}
			
			$where .= $condition;
		}
		
		return $where;
	}
	
}